package com.ethanwoo.generator.generator;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;

import com.ethanwoo.generator.Constants;
import com.ethanwoo.generator.domain.Column;
import com.ethanwoo.generator.domain.Table;
import com.ethanwoo.generator.service.TemplateService;
import com.ethanwoo.generator.utils.JavaTypeNameTranslator;
import com.ethanwoo.generator.utils.JdbcTypeNameTranslator;
import com.ethanwoo.generator.utils.Messages;
import com.ethanwoo.generator.utils.UtilsString;
import com.ethanwoo.generator.utils.ZipUtil;

/**
 * @author Wu,Yang
 */
public class Generator {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	// Database connection params declare
	private static String driverClassName, url, username, password;

	// Base configration
	private static String saveBasePath, projectName, tableNamesString, tablesSeq, packagePrefix;

	// Absoulute package path
	private static String projectPath, javaSrcPath, relativeRootPath, projectPackage, commonsPackage;

	// Absoulute save path
	private static String domainPath, daoPath, daoImplPath, daoImplSqlMapPath, servicePath, serviceImplPath;

	// Spring service
	private static TemplateService templateService;

	// Spring jdbcTemplete
	private static SimpleJdbcTemplate simpleJdbcTemplate;

	// Internel resource
	private static final Messages internel = new Messages(Constants.INTERNER_MESSAGE);

	// start up
	static {
		Messages config = new Messages("config");

		// Database connection params declare
		driverClassName = config.getString("jdbc.driverClassName");
		url = config.getString("jdbc.url");
		username = config.getString("jdbc.username");
		password = config.getString("jdbc.password");

		// Base configration
		saveBasePath = config.getString("config.savepath");
		projectName = config.getString("config.project.name");
		tableNamesString = config.getString("config.project.tables");
		tablesSeq = config.getString("config.project.tables.seq");
		packagePrefix = config.getString("config.project.package.prefix");
		commonsPackage = config.getString("config.project.package.commons.prefix");

		// Absoulute package path
		projectPath = saveBasePath + File.separator + projectName;
		javaSrcPath = projectPath + File.separator + Constants.JAVA_SOURCE_PATH;
		relativeRootPath = new StringBuffer(javaSrcPath).append(File.separator).append(
				packagePrefix.replace(".", File.separator)).append(File.separator).append(projectName).append(
				File.separator).toString();
		projectPackage = packagePrefix + "." + projectName;

		// Absoulute save path
		domainPath = relativeRootPath + "domain";
		daoPath = relativeRootPath + "dao";
		daoImplPath = daoPath + File.separator + "ibatis";
		daoImplSqlMapPath = daoImplPath + File.separator + "maps";
		servicePath = relativeRootPath + "service";
		serviceImplPath = servicePath + File.separator + "impl";

		ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "spring-context.xml" });
		BeanFactory factory = (BeanFactory) context;

		templateService = (TemplateService) factory.getBean("templateService");
		simpleJdbcTemplate = (SimpleJdbcTemplate) factory.getBean("simpleJdbcTemplete");

	}

	public void run() {

		Date startTime = new Date();

		if (StringUtils.isBlank(tableNamesString) || "all".equalsIgnoreCase(tableNamesString)) {
			//String queryTablesSql = "select * from user_tables order by table_name";
			String queryTablesSql = "show tables";

			List<Map<String, Object>> mapList = simpleJdbcTemplate.queryForList(queryTablesSql);

			List<String> tableNamesList = new ArrayList<String>();
			for (Map<String, Object> map : mapList) {
				Object tableName = map.get("TABLE_NAME");
				if (null != tableName) {
					tableNamesList.add(tableName.toString());
				}
			}
			tableNamesString = StringUtils.join(tableNamesList, ",");
		}

		List<Table> tableList = new ArrayList<Table>();
		for (String table : tableNamesString.split(",")) {
			tableList.add(generateEntity(UtilsString.removeQuote(table)));
		}

		String package_iBATIS = new StringBuffer().append(StringUtils.replace(packagePrefix, ".", "/")).append("/")
				.append(projectName).toString();

		String fileNameSqlMapConfig = "sqlmap-config" + Constants.FILE_XML_SUFFIX;
		String fileNameFacade = "Facade" + Constants.FILE_JAVA_SUFFIX;
		String fileNameFacadeImpl = "FacadeImpl" + Constants.FILE_JAVA_SUFFIX;

		Map<String, Object> model = new HashMap<String, Object>();
		model.put("packagePrefix", projectPackage);
		model.put("package_iBATIS", package_iBATIS);
		model.put("tableList", tableList);

		// page
		String pageSqlMapConfig = templateService.getContent("sqlMapConfig.ftl", model);
		String pageFacade = templateService.getContent("facade.ftl", model);
		String pageFacadeImpl = templateService.getContent("facadeImpl.ftl", model);

		// Write to file
		generateFile(javaSrcPath, fileNameSqlMapConfig, pageSqlMapConfig);
		generateFile(servicePath, fileNameFacade, pageFacade);
		generateFile(serviceImplPath, fileNameFacadeImpl, pageFacadeImpl);

		if (StringUtils.equalsIgnoreCase(internel.getString("config.project.zip"), "true")) {
			logger.info(internel.getString("log.zip.going"));
			ZipUtil.zip(projectPath, projectPath + ".zip");
			logger.info(internel.getString("log.zip.end"));
		}

		Date endTime = new Date();

		logger.info(internel.getString("log.generator.run.time", (endTime.getTime() - startTime.getTime()) / 1000));
	}

	public Table generateEntity(String tableName) {
		if (StringUtils.isBlank(tableName)) {
			return null;
		}

		tableName = tableName.toUpperCase();
		String tableJavaName = UtilsString.capitalize(tableName);

		Table t = new Table();
		t.setName(tableName);
		t.setJavaName(tableJavaName);
		t.setVariableName(UtilsString.toLowerCaseFirstLetter(tableJavaName));

		// fileName
		String domainFileName = tableJavaName + Constants.FILE_JAVA_SUFFIX;
		String daoFileName = tableJavaName + "Dao" + Constants.FILE_JAVA_SUFFIX;
		String daoImplFileName = tableJavaName + "DaoSqlMapImpl" + Constants.FILE_JAVA_SUFFIX;
		String daoImplSqlMapFileName = tableName + "_SqlMap" + Constants.FILE_XML_SUFFIX;
		String serviceFileName = tableJavaName + "Service" + Constants.FILE_JAVA_SUFFIX;
		String serviceImplFileName = tableJavaName + "ServiceImpl" + Constants.FILE_JAVA_SUFFIX;

		try {
			Class.forName(driverClassName);

			Connection conn = DriverManager.getConnection(url, username, password);
			Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(new StringBuffer("select * from ").append(tableName).toString());
			ResultSetMetaData tsmd = rs.getMetaData();

			// DatabaseMetaData databaseMetaData = new OracleDatabaseMetaData((OracleConnection) new
			// OracleConnectionWrapper());

			// gen Columns
			List<Column> columnList = new ArrayList<Column>();
			Set<String> packageImportList = new HashSet<String>();
			for (int i = 1; i <= tsmd.getColumnCount(); i++) {
				Column column = new Column();
				column.setName(tsmd.getColumnName(i).toUpperCase());
				column.setTypeName(tsmd.getColumnTypeName(i));
				column.setJavaName(StringUtils.capitalize(tsmd.getColumnName(i).toLowerCase()));
				column.setLowerCaseName(tsmd.getColumnName(i).toLowerCase());

				String javaTypeFullName = JavaTypeNameTranslator.getJavaTypeName(tsmd, i);

				int lastIndex = javaTypeFullName.lastIndexOf('.');
				if (lastIndex != -1 && !javaTypeFullName.startsWith("java.lang.")) {
					packageImportList.add(javaTypeFullName);
				}

				column.setJavaType(javaTypeFullName.substring(lastIndex + 1));
				column.setJavaTypeName(javaTypeFullName);
				column.setJdbcType(JdbcTypeNameTranslator.getJdbcTypeName(tsmd.getColumnType(i)));

				columnList.add(column);
			}
			String seq = "seq_xxx";
			if (StringUtils.isNotBlank(tablesSeq)) {
				seq = tablesSeq;
			}
			t.setSeq(seq);
			t.setColumeList(columnList);
			Arrays.sort(packageImportList.toArray());

			// Initialize a model
			Map<String, Object> model = new HashMap<String, Object>();
			model.put("packagePrefix", projectPackage);
			model.put("commonsPackagePrefix", commonsPackage);
			model.put("table", t);
			model.put("packageImportList", packageImportList);
			model.put("packageImportList", packageImportList);
			model.put("generatorTime", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm"));

			// Generate page content
			String domainPageContent = templateService.getContent("domain.ftl", model);
			String daoPageContent = templateService.getContent("dao.ftl", model);
			String daoImplPageContent = templateService.getContent("daoImpl.ftl", model);
			String daoImplSqlMapPageContent = "";
			String servicePageContent = "";
			String serviceImplPageContent = "";
			if (StringUtils.contains(driverClassName, "oracle")) {
				daoImplSqlMapPageContent = templateService.getContent("daoImplSqlMapForOracle.ftl", model);
				servicePageContent = templateService.getContent("serviceForOracle.ftl", model);
				serviceImplPageContent = templateService.getContent("serviceImplForOracle.ftl", model);
			}

			if (StringUtils.contains(driverClassName, "mysql")) {
				daoImplSqlMapPageContent = templateService.getContent("daoImplSqlMapForMysql.ftl", model);
				servicePageContent = templateService.getContent("serviceForMysql.ftl", model);
				serviceImplPageContent = templateService.getContent("serviceImplForMysql.ftl", model);
			}

			// Write to file
			generateFile(domainPath, domainFileName, domainPageContent);
			generateFile(daoPath, daoFileName, daoPageContent);
			generateFile(daoImplPath, daoImplFileName, daoImplPageContent);
			generateFile(daoImplSqlMapPath, daoImplSqlMapFileName, daoImplSqlMapPageContent);
			generateFile(servicePath, serviceFileName, servicePageContent);
			generateFile(serviceImplPath, serviceImplFileName, serviceImplPageContent);

		} catch (ClassNotFoundException e) {
			logger.error(e.getMessage());
		} catch (SQLException e) {
			logger.error(e.getMessage());
		}

		StringBuffer fileBuffer = new StringBuffer();
		fileBuffer.append("[").append(domainFileName).append(",");
		fileBuffer.append(daoFileName).append(",");
		fileBuffer.append(daoImplFileName).append(",");
		fileBuffer.append(daoImplSqlMapFileName).append(",");
		fileBuffer.append(serviceFileName).append(",");
		fileBuffer.append(serviceImplFileName).append("]");

		logger.info(internel.getString("log.generator.run.entity", tableName, fileBuffer.toString()));

		return t;
	}

	/**
	 * @param path: 文件保存目录路径
	 * @param fileName： 文件名称
	 * @param page：文件内容
	 */
	public void generateFile(String path, String fileName, String content) {
		String savePath = UtilsString.concat(path, fileName);
		try {
			FileUtils.writeStringToFile(new File(savePath), content, Constants.FILE_ENCODING);
		} catch (IOException e) {
			logger.error(e.getMessage());
		}

		logger.debug(internel.getString("log.generator.run.file", fileName, savePath));
	}
}
